﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Windows.Input;
using OpenLS.Spreadsheet.UI.Internals;

namespace OpenLS.Spreadsheet.UI
{
    /// <summary>
    /// Represents the view model for a worksheet
    /// </summary>
    public class WorksheetViewModel : INotifyPropertyChanged
    {
        private readonly Worksheet _worksheet;
        private readonly List<WorksheetPane> _processed = new List<WorksheetPane>();
        private bool inActiveCellChange;

        private ObservableCollection<object> _references = new ObservableCollection<object>();
       // static WeakDictionary<Worksheet, WorksheetViewModelValue> _liveModels = new WeakDictionary<Worksheet, WorksheetViewModelValue>();
        private Formula _formula;
        private bool _deferScrollUpdate;

        public static WorksheetViewModel GetModel (Worksheet worksheet)
        {
            Contract.Requires(worksheet != null);
            Contract.Ensures(Contract.Result<WorksheetViewModel>() != null);
            WorksheetViewModel result = (WorksheetViewModel) worksheet.ViewModel;
            if (result != null)
                return result;

            result = new WorksheetViewModel(worksheet);
            worksheet.ViewModel = result;
            return result;
        }
        private WorksheetViewModel(Worksheet worksheet)
        {
            Contract.Requires(worksheet != null);
          //  Debug.WriteLine("Creating WorksheetViewModel for " + worksheet.Name);
            _worksheet = worksheet;
            WeakEventHelper.AddWeakPropertyChanged(_worksheet, OnWorksheetChanged);
            AddPane(worksheet.ScrollPane);
            if (_worksheet.View != null)
            {
                WeakEventHelper.AddWeakPropertyChanged(_worksheet.View, OnViewChanged);
                if (_worksheet.View.Pane != null)
                {
                    WeakEventHelper.AddWeakPropertyChanged(_worksheet.View, OnPaneChanged);
                }
            }
        }

        public int MinRow
        {
            get
            {
                if (Worksheet.BottomLeftPane == null)
                    return 0;
                if (Worksheet.View.Pane == null)
                    return 0;
                if (Worksheet.View.Pane.State == PaneState.Split)
                    return 0;
                return Worksheet.BottomLeftPane.MinTop;
            }
        }

        public IEnumerable<object> ValidationValues
        {
            get
            {
                if (ActiveCell == null)
                    return null;
                DataValidation dataValidation = Enumerable.FirstOrDefault(ActiveCell.Worksheet.DataValidations,
                                                                          v => v.Range.Contains(ActiveCell));
                if (dataValidation == null)
                    return null;
                if (dataValidation.Type != DataValidationType.List)
                    return null;
                return dataValidation.GetValues(ActiveCell.Worksheet);
            }
        }

        public object SelectedValidationValue
        {
            get
            {
                if (ActiveCell == null)
                    return null;
                return ActiveCell.Value;
            }
            set
            {
                if (inActiveCellChange)
                    return;
                ActiveCell.Value = value;
            }
        }

        protected Cell ActiveCell
        {
            get { return _worksheet.ActiveCell; }
        }

        public bool HasValidationComboBox
        {
            get { return ValidationValues != null; }
        }

        public int MinColumn
        {
            get
            {
                if (Worksheet.TopRightPane == null)
                    return 0;
                if (Worksheet.View.Pane == null)
                    return 0;
                if (Worksheet.View.Pane.State == PaneState.Split)
                    return 0;
                return Worksheet.TopRightPane.MinLeft;
            }
        }

        public int MaxColumn
        {
            get
            {
                if (!IsFrozen)
                    return Worksheet.ScrollPane.Right;
                return Math.Max(Worksheet.ScrollPane.MaxColumn, MinColumn + 1);
            }
        }

        public int MaxRow
        {
            get
            {
                if (!IsFrozen)
                    return Worksheet.ScrollPane.Bottom;
                return Math.Max(Worksheet.ScrollPane.MaxRow, MinRow + 1);
            }
        }
        public int MaxRowPosition
        {
            get
            {
                return this.Worksheet.ScrollPane.MaxRowPosition;
                if (!IsFrozen)
                    return Math.Max(Math.Max(Worksheet.BottomRight.Row, Worksheet.ScrollPane.Bottom) - Worksheet.ScrollPane.RowCount, Worksheet.ScrollPane.MinTop + 1);
                return Math.Max(Worksheet.ScrollPane.MaxRow, MinRow + 1);
            }
        }

        public int MaxColumnPosition
        {
            get
            {
                return Worksheet.ScrollPane.MaxColumnPosition;
                if (!IsFrozen)
                    return Math.Max(Math.Max(Worksheet.BottomRight.Column, Worksheet.ScrollPane.Right) - Worksheet.ScrollPane.ColumnCount, Worksheet.ScrollPane.MinLeft + 1);
                return Math.Max(Worksheet.ScrollPane.MaxColumn, MinColumn + 1);
            }
        }

        private bool IsFrozen
        {
            get
            {
                if (Worksheet.BottomLeftPane  == null && Worksheet.TopRightPane == null)
                    return false;
                if (Worksheet.View.Pane == null)
                    return false;
                if (Worksheet.View.Pane.State == PaneState.Split)
                    return false;
                return true;
            }
        }

        public Worksheet Worksheet
        {
            get { return _worksheet; }
        }

        public bool IsActiveCellFormulaVisible
        {
            get
            {
                Cell cell = _worksheet.ActiveCell;
                if (!_worksheet.IsProtected (p => true))
                    return true;
                return cell == null || !cell.IsFormulaHidden;
            }
        }

        public ObservableCollection<object> References
        {
            get
            {
//                Debug.WriteLine("Getting References " + _references.GetHashCode());
                return _references;
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        private void OnWorksheetPaneChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "MaxColumn":
                case "MinColumn":
                case "MinRow":
                case "MaxRow":
                    OnScrollChanged();
                    break;
            }
        }

        private void OnPaneChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "State")
            {
                OnScrollChanged();
            }
        }

        private void OnScrollChanged()
        {
            if (DeferScrollUpdate)
                return;
            Debug.WriteLine(string.Format("OnScrollChanged {0} {1} {2} {3} {4} {5}", MinRow, MinColumn, MaxRow, MaxColumn, MaxRowPosition, MaxColumnPosition) );
            NotifyPropertyChanged("MaxRow");
            NotifyPropertyChanged("MaxColumn");
            NotifyPropertyChanged("MaxColumnPosition");
            NotifyPropertyChanged("MaxRowPosition");
            NotifyPropertyChanged("MinRow");
            NotifyPropertyChanged("MinColumn");
        }

        private void OnViewChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Pane")
            {
                var view = (SheetView) sender;
                if (view.Pane != null)
                    WeakEventHelper.AddWeakPropertyChanged(view.Pane, OnPaneChanged);
            }
        }

        private void AddPane(WorksheetPane pane)
        {
            if (_processed.Contains(pane))
                return;
            WeakEventHelper.AddWeakPropertyChanged(pane, OnWorksheetPaneChanged);
            _processed.Add(pane);
        }

        private void OnWorksheetChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "View":
                    NotifyPropertyChanged("ScrollTop");
                    NotifyPropertyChanged("ScrollLeft");
                    {
                        if (Worksheet.View != null)
                            WeakEventHelper.AddWeakPropertyChanged(Worksheet.View, OnViewChanged);
                    }
                    break;
                case "ActiveCell"
                    :
                    inActiveCellChange = true;
                    NotifyPropertyChanged("IsActiveCellFormulaVisible");
                    NotifyPropertyChanged("ValidationValues");
                    inActiveCellChange = false;
                    break;
                case "Protection":
                    NotifyPropertyChanged("IsActiveCellFormulaVisible");
                    break;
                case "ScrollPane":
                    AddPane(Worksheet.ScrollPane);
                    NotifyPropertyChanged("MinRow");
                    NotifyPropertyChanged("MinColumn");
                    break;
            }
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            Contract.Requires(
                GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) !=
                null);
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }


        public Formula Formula
        {
            get { return _formula; }
            set { _formula = value; NotifyPropertyChanged("Formula"); }
        }

        internal bool DeferScrollUpdate
        {
            get
            {
                return this._deferScrollUpdate;
            }
            set {
                if (_deferScrollUpdate == value)
                    return;
                Debug.WriteLine("Setting DeferScrollUpdate to " + value);
                this._deferScrollUpdate = value;
                this.OnScrollChanged();
            }
        }
    }
}